/* MP3 module Test */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "swis.h"

#define AudioMPEG (0x52180)
#define BLOCKSIZE (4096)

typedef struct
{
	char	id[4];
	int	size;
	char	wavefmt[8];
	int	fmtsize;
	short	format;
	short	channels;
	int	samplerate;
	int	bytespersec;
	short	blocksize;
	short	bitspersample;
	char	data[4];
	int	datasize;
} File_Hdr;

typedef struct
{
	int size;
	int start;
	int free;
	int finished;
	int dummy[12];
	char data[1];
} mp3buf;

typedef struct
{
	int size;
	int start;
	int free;
	int finished;
	int dummy[12];
	short data[1];
} wavbuf;

typedef struct
{
	int version;
	int layer;
	int bitrate;
	int samplerate;
	int channels;
} mp3info;

typedef struct Converter
{
	FILE*		mp3f;
	mp3buf*		mp3b;
	mp3info*	info;
	FILE*		wavf;
	wavbuf*		wavb;
	File_Hdr	wavh;
} converter;

static converter Cvrt;

static _kernel_oserror* RMEnsure(const char* module, const char* filename)
{
	// Check if module present in RMA
	if (!_swix(OS_Module, _IN(0)|_IN(1), 18, module))
		return NULL;

	// RMLoad module
	return _swix(OS_Module, _IN(0)|_IN(1), 1, filename);
}

static int inwavbuf(wavbuf* wavb)
{
	int n;

	n = wavb->free - wavb->start;
	if (n < 0)
		n += wavb->size;

	return n;
}

static void converter_WriteInitialHeader(converter* cvrt, int samplerate, int channels)
{
	strncpy(cvrt->wavh.id, "RIFF", 4);
	cvrt->wavh.size = sizeof(File_Hdr) - 8;
	strncpy(cvrt->wavh.wavefmt, "WAVEfmt ", 8);
	cvrt->wavh.fmtsize = 16;
	cvrt->wavh.format = 1; //PCM
	cvrt->wavh.channels = channels;
	cvrt->wavh.samplerate = samplerate;
	cvrt->wavh.bytespersec = cvrt->wavh.samplerate*cvrt->wavh.channels*2;
	cvrt->wavh.blocksize = cvrt->wavh.channels*2;
	cvrt->wavh.bitspersample = 16;
	strncpy(cvrt->wavh.data, "data", 4);
	cvrt->wavh.datasize = 0;

	fseek(cvrt->wavf, 0, SEEK_SET);
	fwrite(&cvrt->wavh, 1, sizeof(cvrt->wavh), cvrt->wavf);
}

static void converter_WriteUpdatedHeader(converter* cvrt)
{
	cvrt->wavh.size = sizeof(File_Hdr) - 8 + cvrt->wavh.datasize;

	fseek(cvrt->wavf, 0, SEEK_SET);
	fwrite(&cvrt->wavh, 1, sizeof(cvrt->wavh), cvrt->wavf);
}

static int converter_WriteWaveBuffer(converter* cvrt)
{
	int n;
	int free = cvrt->wavb->free; // Because the value may change in the background

	if (cvrt->wavb->start == free)
		return 0;

	if(cvrt->wavb->start > free)
	{
		// Write filled part from start to size of buffer
		n = fwrite(&cvrt->wavb->data[cvrt->wavb->start]
				, 2
				, cvrt->wavb->size - cvrt->wavb->start
				, cvrt->wavf);
		// Write filled part from 0 to free
		n += fwrite(&cvrt->wavb->data[0]
				, 2
				, free
				, cvrt->wavf);
	}
	else
	{
		// Write filled part from start to free
		n = fwrite(&cvrt->wavb->data[cvrt->wavb->start]
				, 2
				, free - cvrt->wavb->start
				, cvrt->wavf);
	}
	n *= 2;

	// Empty the buffer
	cvrt->wavb->start = free;

	cvrt->wavh.datasize += n;

	return n;
}

static void converter_ReadMP3Block(converter* cvrt, int blksize)
{
	int start = cvrt->mp3b->start; // Because value may change in the background
	int n;

	n = start - cvrt->mp3b->free;
	if (n <= 0)
		n += cvrt->mp3b->size;

	if(feof(cvrt->mp3f))
	{
		// If end of file reached, mark the input buffer as completed
		cvrt->mp3b->finished = 1;
	}
	else if(n > blksize)
	{
		// Free part may actually wrap from end to beginning of the buffer
		if (blksize > (cvrt->mp3b->size - cvrt->mp3b->free))
			blksize = cvrt->mp3b->size - cvrt->mp3b->free;
		// If enough free space inf buffer, read a block of data
		// but beware of never filling the buffer completely
		// as it would be interpreted as an empty buffer
		n = fread(&cvrt->mp3b->data[cvrt->mp3b->free], 1, blksize, cvrt->mp3f);
		// Update position of free pointer but use an intermediate variable
		// to avoid temporary corruption of free pointer (AudioMPEG
		// is working in the background and may work on the buffer
		// in the middle of the computation of the new free position
		n += cvrt->mp3b->free;
		if (n >= cvrt->mp3b->size)
			n = 0;
		cvrt->mp3b->free = n;
	}
}

int main(int argc, char **argv)
{
	int t;
	int xcvb = 0;
	int code = (int) clock(); // setup a unique code unlikely to be used by other tasks
	int blksize = BLOCKSIZE;
	_kernel_oserror* e = NULL;

	if (argc != 3)
	{
		printf("Syntax: MP3Cvrt <infile> <outfile>\n");
		return EXIT_FAILURE;
	}

	e = RMEnsure("AudioMPEG", "System:Modules.Audio.MP3.AudioMPEG");
	if (e)
	{
		printf("%s\n", e->errmess);
		return EXIT_FAILURE;
	}

	Cvrt.info = NULL;
	Cvrt.mp3f = fopen(argv[1], "r");
	if (!Cvrt.mp3f)
	{
		printf("Could not open input file %s\n", argv[1]);
		return EXIT_FAILURE;
	}

	Cvrt.wavf = fopen(argv[2], "w");
	if (!Cvrt.wavf)
	{
		printf("Could not open output file %s\n", argv[2]);
		fclose(Cvrt.mp3f);
		return EXIT_FAILURE;
	}

	// Init an MP3 stream with AudioMPEG_Init
	e = _swix(AudioMPEG+0, _IN(0)|_OUT(0)|_OUT(1), code, &Cvrt.mp3b, &Cvrt.wavb);
	if (e)
	{
		printf("%s\n", e->errmess);
		fclose(Cvrt.wavf);
		fclose(Cvrt.mp3f);
		return EXIT_FAILURE;
	}

	if (blksize > (Cvrt.mp3b->size >> 2))
		blksize = Cvrt.mp3b->size >> 2;

	// Convertion loop
	t = clock();

	// Fill input buffer till AudioMPEG can provide onfo on the stream
	do
	{
		converter_ReadMP3Block(&Cvrt, blksize);
		// AudioMPEG_Info
		Cvrt.info = (mp3info*) _swi(AudioMPEG+2, _IN(0)|_RETURN(0), code);
	}
	while (   (((int) Cvrt.info == -1) || ((int) Cvrt.info == 0) || ((int) Cvrt.info == 1))
	       && (clock() < t + 5*CLK_TCK));

	if (((int) Cvrt.info == -1) || ((int) Cvrt.info == 0) || ((int) Cvrt.info == 1))
	{
		printf("Input file doesn't appear to be a valid audio MPEG stream\n");
	}
	else
	{
		printf("MP3 version: %d layer: %d bitrate: %dkbit/s samplerate: %dHz channels: %d\n"
			, Cvrt.info->version
			, Cvrt.info->layer
			, Cvrt.info->bitrate
			, Cvrt.info->samplerate
			, Cvrt.info->channels);

		converter_WriteInitialHeader(&Cvrt, Cvrt.info->samplerate, Cvrt.info->channels);

		while(!Cvrt.wavb->finished || inwavbuf(Cvrt.wavb) != 0)
		{
			converter_ReadMP3Block(&Cvrt, blksize);

			xcvb += converter_WriteWaveBuffer(&Cvrt);
			printf("\r%d KB", xcvb >> 10);
		}

		converter_WriteUpdatedHeader(&Cvrt);
		printf("\n");
	}
	t = clock() - t;

	// AudioMPEG_QUIT
	_swi(AudioMPEG+1, _IN(0), code);

	fclose(Cvrt.mp3f);
	fclose(Cvrt.wavf);

	// *Settype <outfile> FB1
	_swix(OS_File, _IN(0)|_IN(1)|_IN(2), 18, argv[2], 0xFB1);

	printf("Time: %d secs\n", t/CLK_TCK);


	return EXIT_SUCCESS;
}
